using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using CS2.Core.Parsing;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Util;
using Directory=Lucene.Net.Store.Directory;
using System.Linq;
using Nemerle.Utility;

namespace CS2.Core.Indexing
{
    public class IndexingService : IIndexingService
    {
        updatingLock = object();
        filesWaitingToBeIndexed : ISynchronizedStringSet = SynchronizedStringSet();
        
        [Accessor] parsers : array[IParsingService];
        [Accessor] mutable indexDirectory : Directory;
        [Accessor] mutable addedFilesSinceLastUpdate : int;
        [Accessor] mutable deletedFilesSinceLastUpdate : int;
        [Accessor] mutable documentCount : int;

        mutable exclusions : array[Regex] = array[];

        public this(indexDirectory : Directory, parsers : array[IParsingService]) {
            this.indexDirectory = indexDirectory;
            this.parsers = parsers;
    
            TryCreateIndex();
            
            using(indexReader = IndexReader.Open(indexDirectory, true))
                documentCount = indexReader.MaxDoc();
        }
        
        private TryCreateIndex() : void {
            unless(IndexReader.IndexExists(indexDirectory))
            {
                def writer = IndexWriter(indexDirectory, StandardAnalyzer(Version.LUCENE_29), true, IndexWriter.MaxFieldLength.UNLIMITED);
                writer.Optimize();
                writer.Close();
            }
        }

        public IsWaitingForFilesToBeIndexed : bool {
            get { filesWaitingToBeIndexed.Any() }
        }

        public Exclusions : array[string] {
            set { exclusions = value.Select(v => Regex(v |> Regex.Escape)).ToArray() }
        }

        public RequestIndexing(file : FileInfo) : void {
            when(IsValidFileSystemEntryToBeIndexed(file))
                _ = filesWaitingToBeIndexed.Add(file.FullName);
        }

        public RequestIndexing(directory : DirectoryInfo) : void {
            when(IsValidFileSystemEntryToBeIndexed(directory))
                _ = ThreadPool.QueueUserWorkItem(DoIndexDirectory, directory)
        }
        
        private DoIndexDirectory(directory : object) : void {
            foreach(parser in parsers)
            foreach(extension in parser.SupportedFileExtensions)
            foreach(file in (directory :> DirectoryInfo).EnumerateFiles(string.Format("*{0}", extension), SearchOption.AllDirectories))
                RequestIndexing(file);
        }

        public UpdateIndex() : void {
            when(Monitor.TryEnter(updatingLock))
                try DoUpdateIndex();
                finally Monitor.Exit(updatingLock);
        }
        
        private DoUpdateIndex() : void {
            def filesUndergoingIndexing = filesWaitingToBeIndexed.CloneAndClear();

            mutable addedFiles = 0;
            
            RemoveOldEntries(filesUndergoingIndexing);

            when(filesUndergoingIndexing.Any())
            {
                def indexWriter = IndexWriter(indexDirectory, StandardAnalyzer(Version.LUCENE_29), false, IndexWriter.MaxFieldLength.UNLIMITED);

                foreach(fileName in filesUndergoingIndexing)
                    when(Index(FileInfo(fileName), indexWriter))
                        addedFiles++;

                documentCount = indexWriter.MaxDoc();
                indexWriter.Optimize();
                indexWriter.Close();
            }

            addedFilesSinceLastUpdate = addedFiles;

            OnIndexingCompleted();
        }

        public event IndexingCompleted : Action[IndexingCompletedEventArgs];

        private IsValidFileSystemEntryToBeIndexed(entry : FileSystemInfo) : bool {
            if(!entry.Exists) false;
            else if(entry.Attributes & FileAttributes.Hidden == FileAttributes.Hidden) false;
            else if(exclusions.Any <| _.IsMatch(entry.FullName)) false;
            else true;
        }

        private OnIndexingCompleted() : void {
            when(IndexingCompleted != null)
                IndexingCompleted(IndexingCompletedEventArgs(addedFilesSinceLastUpdate, deletedFilesSinceLastUpdate, documentCount));
        }

        private RemoveOldEntries(filesUndergoingIndexing : ISynchronizedStringSet) : void {        
            using(indexReader = IndexReader.Open(indexDirectory, false)) {
                mutable deletedCount;
                
                for(mutable i = 0; i < indexReader.MaxDoc(); i++) {
                    def doc = indexReader.Document(i);
                    def id = doc.GetField(FieldFactory.IdFieldName).StringValue();
                    def filePath = IdIdentifierUtilities.GetPathFromIdentifier(id);

                    _ = filesUndergoingIndexing.Remove(filePath);

                    def fileExists = File.Exists(filePath);

                    when(!fileExists || IdIdentifierUtilities.GetIdentifierFromFile(FileInfo(filePath)) != id) {
                        indexReader.DeleteDocument(i);
                        deletedCount++;

                        when(fileExists)
                            _ = filesUndergoingIndexing.Add(filePath);
                    }
                }

                deletedFilesSinceLastUpdate = deletedCount
            }
        }

        private Index(file : FileInfo, indexWriter : IndexWriter) : bool {
            mutable document;
            
            match(parsers.Where(p => p.SupportedFileExtensions.Contains <| file.Extension).FirstOrDefault(p => p.TryParse(file, out document))) {
                | p when p != null =>   document.Add(FieldFactory.CreateIdField(IdIdentifierUtilities.GetIdentifierFromFile(file)));
                                        document.Add(FieldFactory.CreatePathField(file.FullName));
                                        document.Add(FieldFactory.CreateFileNameField(file.Name));
                                        document.Add(FieldFactory.CreateLanguageField(p.LanguageName));
                                           
                                        using(def reader = StreamReader(file.FullName)) {
                                            document.Add(FieldFactory.CreateSourceField(reader));
                                            indexWriter.AddDocument(document, p.Analyzer);
                                        }

                                        true; 
                | _ => false;
            }
        }
    }
}